<!--
 * @Description: 图片裁剪
 * @Author: ZhangHan
 * @Date: 2025-06-16 17:24:59
 * @LastEditTime: 2025-06-17 14:42:25
 * @LastEditors: ZhangHan
-->
<template>
  <div class="w-full h-full flex-col items-center justify-center">
    <!-- action="#" #在实战中替换为实际的后台接口 -->
    <ElUpload
      v-model:file-list="fileList"
      class="upload-demo"
      action="#"
      multiple
      :limit="1"
      accept="image/*"
      list-type="picture-card"
      :on-change="onChange"
      :auto-upload="false"
    >
      <el-button type="primary">Click to upload</el-button>
      <template #tip>
        <div class="el-upload__tip">
          xxxxxxxxxxxxxxxxxxxxx
        </div>
      </template>
    </ElUpload>
    <div :style="{ height: '600px', width: '600px', marginBottom: '30px' }">
      <VueCropper
        ref="cropper"
        :info="false"
        :infoTrue="options.infoTrue"
        :img="options.img"
        :autoCrop="options.autoCrop"
        :autoCropWidth="options.autoCropWidth"
        :autoCropHeight="options.autoCropHeight"
        :fixedBox="options.fixedBox"
        :mode="options.cropperMode"
        :centerBox="options.centerBox"
        :enlarge="options.enlarge"
        :fixedNumber="options.fixedNumber"
        outputType="png"
        @realTime="realTime"
      />
    </div>
    <ElRow :style="{ textAlign: 'center' }">
      <ElCol :span="6">
        <ElButton type="default" :icon="ZoomOut" @click="changeScale(1)">
          放大
        </ElButton>
      </ElCol>
      <ElCol :span="6"
        ><ElButton type="default" :icon="RefreshRight" @click="rotateLeft"
          >左旋转</ElButton
        ></ElCol
      >
      <ElCol :span="6"
        ><ElButton type="default" :icon="RefreshLeft" @click="rotateRight"
          >右旋转</ElButton
        ></ElCol
      >
      <ElCol :span="6">
        <ElButton :icon="ZoomIn" type="default" @click="changeScale(-1)">
          缩小
        </ElButton>
      </ElCol>
    </ElRow>
    <div>
      <ElButton @click="handleSubmit()">提交事件</ElButton>
    </div>
  </div>
</template>

<script setup lang="ts">
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
import { ref, reactive, onMounted } from "vue";
import { ElButton, ElRow, ElCol, ElUpload } from "element-plus";
import {
  RefreshRight,
  RefreshLeft,
  ZoomIn,
  ZoomOut,
} from "@element-plus/icons-vue";

const previews = ref();
const cropper = ref();
const fileName = ref();
const options = reactive({
  img: "", //裁剪图片的地址
  autoCrop: true, //是否默认生成截图框
  autoCropWidth: undefined, //默认生成截图框宽度
  autoCropHeight: undefined, //默认生成截图框高度
  enlarge: undefined, // 图片根据截图框输出比例倍数
  fixedBox: true, //是否固定截图框大小 不允许改变
  previewsCircle: false, //预览图是否是原圆形
  centerBox: true, //截图框是否被限制在图片里面
  fixedNumber: [1, 1], // 截图框的宽高比例
  infoTrue: true,
  title: "修改图片",
  cropperMode: "contain",
});

//上传图片组件数据
const fileList = ref<any>([]);

interface CropperProps {
  action?: string; //上传接口
  fixedWidthNumber?: any; // 截图框的宽高比例
  fixedHeightNumber?: any; // 截图框的宽高比例
}
const props = withDefaults(defineProps<CropperProps>(), {
  action: "后台接口",
  fixedWidthNumber: 1,
  fixedHeightNumber: 1,
});


// //图片上传前回调
const onChange = (file: any, list: any) => {
  console.log("🚀 ~ beforeUpload ~ file:any, fileList:any:", file, list);
  if (file) {
    fileList.value[0] = file;
    options.img = file.url;
    fileName.value = file.name;
    const imgData: any = getImageData(600, file?.raw, [
      props.fixedWidthNumber,
      props.fixedHeightNumber,
    ]);
    options.autoCropWidth = imgData.width;
    options.autoCropHeight = imgData.height;
    options.enlarge = imgData.ratio;
    options.fixedNumber = [props.fixedWidthNumber, props.fixedHeightNumber];
  }
};

/**
 * @Description: 计算原图与裁剪框显示的图片的比例,裁剪框的大小(需要传入原图的文件！！！, 裁剪弹框的高度)
 * 实现的功能：
 * 1.如果没有传截图框的比例，那么默认是1:1,那一边短那一边被当成截图框的长度。
 * 2.传入截图框比例，有六种情况，处理原则为截图框比例不会改变的情况下截取原图一边。
 * 三个重要核心：
 * 1.原图与裁剪弹框的比例。
 * 2.原图宽高比例。
 * 3.截图框宽高比例。
 */
function getImageData(maxHeight: number, file: any, fixed: number[]) {
  console.log("🚀 ~ getImageData ~ file:", file);
  return new Promise((resolve, reject) => {
    // 读取文件内容 new FileReader()
    const reader = new FileReader();
    // readAsDataURL: 方法可以将读取到的文件编码成DataURL （这里的reader.result是base64格式）
    reader.readAsDataURL(file);
    // onload:文件读取成功时触发
    reader.onload = () => {
      // 创建一个Image对象
      const image: any = new Image();
      // 定义Image对象的src: image.src = reader.result;  这样做就相当于给浏览器缓存了一张图片。
      image.src = reader.result;
      console.log("🚀 ~ returnnewPromise ~ reader.result:", reader.result);
      // onload:Image对象创建成功时触发
      image.onload = () => {
        let Ratio = 1;
        // 获取原图的宽和高
        const w = image.width;
        const h = image.height;
        // 获取原图的比例
        const imgRatio = w / h;
        //图片长的是那一边
        const imgLong = imgRatio > 1 ? "width" : "height";
        // 计算出原图被缩放到裁剪框缩小的比例
        const wRatio = w / maxHeight;
        const hRatio = h / maxHeight;
        // 按照功能需要，长的一边需要与裁剪弹框一样长，所以比例取长的一边
        Ratio = wRatio >= hRatio ? wRatio : hRatio;
        // 计算截图框的比例
        const fixedRatio = fixed[0] / fixed[1];
        // 截图比例长的是那一边
        const fixedLong = fixedRatio > 1 ? "width" : "height";
        // 初始情况即没有传入裁剪弹框比例
        const imgWidth = w > h ? h / Ratio : w / Ratio;
        let imgData = {
          ratio: Ratio,
          width: imgWidth,
          height: imgWidth,
        };
        // 传入裁剪框比例的六种情况
        if (imgLong === fixedLong) {
          if (imgLong === "width") {
            if (imgRatio > fixedRatio) {
              // 情况1 图的比例和裁剪框的比例都是宽大，但是图片比例大于截图比例。
              // 情况处理：在以一边为裁剪时，图放得下裁剪框，不用换边为裁剪框的基数，以高为基数。
              imgData = {
                ratio: Ratio,
                width: (h / Ratio) * fixedRatio,
                height: h / Ratio,
              };
            } else {
              // 情况2 图的比例和裁剪框的比例都是宽大，但是图片比例小于截图比例。
              // 情况处理：在以一边为裁剪时，图放不下裁剪框，换边为裁剪框的基数，以宽为基数。
              imgData = {
                ratio: Ratio,
                width: w / Ratio,
                height: (h / Ratio) * (w / Ratio / ((h / Ratio) * fixedRatio)),
              };
            }
          } else {
            if (imgRatio < fixedRatio) {
              // 情况3 图的比例和裁剪框的比例都是高大，但是图片比例小于于截图比例。（w/h 小于即大于）
              // 情况处理：在以一边为裁剪时，图放得下裁剪框，不用换边为裁剪框的基数，以宽为基数。
              imgData = {
                ratio: Ratio,
                width: w / Ratio,
                height: w / Ratio / fixedRatio,
              };
            } else {
              // 情况4 图的比例和裁剪框的比例都是高大，但是图片比例大于截图比例。（w/h 小于即大于）
              // 情况处理：在以一边为裁剪时，图放不下裁剪框，换边为裁剪框的基数，以高为基数。
              imgData = {
                ratio: Ratio,
                width: (w / Ratio) * (h / Ratio / (w / Ratio / fixedRatio)),
                height: h / Ratio,
              };
            }
          }
        } else {
          if (imgLong === "width") {
            // 情况5 图的比例大于1，裁剪框的比例小于1。
            // 情况处理：在以一边为裁剪时，图放得下裁剪框，不用换边为裁剪框的基数，以高为基数。
            imgData = {
              ratio: Ratio,
              width: (h / Ratio) * fixedRatio,
              height: h / Ratio,
            };
          } else {
            // 情况5 图的比例小于1，裁剪框的比例大于1。
            // 情况处理：在以一边为裁剪时，图放得下裁剪框，不用换边为裁剪框的基数，以宽为基数。
            imgData = {
              ratio: Ratio,
              width: w / Ratio,
              height: w / Ratio / fixedRatio,
            };
          }
        }
        resolve(imgData);
      };
      image.onerror = () => {
        reject(new Error("Image对象创建失败"));
      };
    };
  });
}

//移动框的事件
const realTime = (data: any) => {
  previews.value = data;
};

//图片缩放
function changeScale(num: number) {
  num = num || 1;
  cropper.value.changeScale(num);
}
//向左旋转
function rotateLeft() {
  cropper.value.rotateLeft();
}
//向右旋转
function rotateRight() {
  cropper.value.rotateRight();
}

// 提交事件
async function handleSubmit() {
  // 提交事件

  cropper.value.getCropData(async (data: any) => {
    //拿到裁剪后的原始数据
     const file = dataURLtoFile(data, fileName.value);
     console.log("🚀 ~ cropper.value.getCropData ~ file:", file)

    //调用接口--传递给后台--从后台再读取新的图片链接并赋值即可
    // const result = await 后台接口({ file });
  });
}

// base 64 转成二进制文件流
const dataURLtoFile = (dataurl: any, filename: any) => {
  var arr = dataurl.split(","),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, { type: mime });
};
</script>
